package com.lct.eq2.util;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * <H3>Provides thread-safe background logging services.</H3>
 * <B>Description:</B><BR>
 * This singleton class provides members for performing logging. It creates and
 * manages a background thread that feeds from a queue of log entries and writes
 * them to a log file. Methods are provided to get a reference to the DailyLog
 * singleton, to create the various levels of log entries, and to close the log
 * file. The log files are written to a default [logs] or a given directory,
 * are named using YYYY-MM-DD.log format, and automatically cycle at day change.
 * <BR>
 * @since JDK v1.3, WISP v4.1
 * @author Kristopher Waite
 * @version 1.0
 * @see DailyLogThread DailyLogThread
 */
public final class DailyLog implements Runnable {
    //=========================================================================
    //                      Declares for Constants
    //=========================================================================
    /**
     * This private class constant holds the name of this class.
     */
    private static final String sC = "DailyLog";
    
    /**
     * This private class constant holds a reference to this singleton object.
     * It causes the instantiation of this singleton object during class initialization.
     */
    private static final DailyLog refSingleton = new DailyLog();
    
    /** This public class constant is used to indicate debug loggging is off. */
    public static final int DEBUGLEVEL_OFF = -1;
    
    /** This public class constant is used to indicate debug loggging is on. */
    public static final int DEBUGLEVEL_ON = 0;
    
    /** This public class constant is used to indicate debug loggging is at a FINE level of detail. */
    public static final int DEBUGLEVEL_FINE = 1;
    
    /** This public class constant is used to indicate debug loggging is at a FINER level of detail. */
    public static final int DEBUGLEVEL_FINER = 2;
    
    /** This public class constant is used to indicate debug loggging is at the FINEST level of detail. */
    public static final int DEBUGLEVEL_FINEST = 3;
    
    //=========================================================================
    //                      Declares for Member Fields
    //=========================================================================
    
    
    /** This private class variable holds the current debug level. */
    protected static int debugLevel;
    
    /**
     * Inidicates whether or not Tivoli messages should be sent. Default is false.
     */
    protected static boolean tivoliOn = false;
    
    /**
     * The maximum number of Tivoli messages to queue before sending. Default is 10.
     */
    protected static int theMaxTivoliQueueSize = 10;
    
    /**
     * The maximum amount of time to wait in milliseconds before the Tivoli queues are emptied.
     * Default is 240,000 (2 minutes).
     */
    protected static long theMaxTivoliQueueWait = 240000;
    
    protected static int theMaxTivoliMessageByteSize = 1024;
    
    private static final String NL = System.getProperty("line.separator");
    
    /**
     * This private instance variable holds a reference to the background thread.
     */
    private Thread theLogWriter;
    
    /**
     * This protected class variable holds the log indentifier that will be
     * combined with the current date to create the log's file name.
     */
    protected static String logID = "DailyLog";
    
    /**
     * This protected intstance variable holds the log directory path.
     */
    protected static String logDirPath;
    
    /**
     * This private class variable holds the SimpleDateFormat "yyyy-MM-dd HH:mm:ss.SSS" for making time stamps.
     */
    private static final SimpleDateFormat sdfDateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    
    /**
     * This protected class variable holds the log file date (i.e. log file name root).
     */
    protected static String logDate = "YYYY-MM-DD";
    
    /**
     * This private class variable holds the thread name used for the previous log entry written.
     * @see #formatEntry formatEntry
     */
    private static String prevThreadName = sC;
    
    /**
     * This List reference will holds a reference to a sychronized LinkedList that
     * is used to store log entries until they can be written to the log file.
     * @see java.util.List List
     * @see java.util.LinkedList LinkedList
     */
    private List theLogQueue = null;
    
    private StringBuffer theTivoliHarmlessMessage = null;
    
    private StringBuffer theTivoliWarningMessage = null;
    
    private StringBuffer theTivoliCriticalMessage = null;
    
    private int theTivoliHarmlessMessageCount = 0;
    private int theTivoliWarningMessageCount = 0;
    private int theTivoliCriticalMessageCount = 0;
    
    private long theLastHarmlessTivoliSendTime = -1;
    private long theLastWarningTivoliSendTime = -1;
    private long theLastCriticalTivoliSendTime = -1;
    
    private String theTivoliChannel = null;
    private String theTivoliMessageHeader = null;
    
    /**
     * This FileWriter reference is used to manage the log file.
     * @see java.io.FileWriter FileWriter
     */
    private FileWriter logFile = null;
    
    /**
     * This boolean flag controls the processing of the run()'ing thread.
     * If "true" [default] the thread is to remain active processing the log queue
     * and writing log entries. If "false" the thread should empty the log queue
     * and close the log file.
     */
    private boolean busy = true;
    
    //=========================================================================
    //                             Constructors
    //=========================================================================
    /**
     * <H4>The constructor for the class, it starts the background thread.</H4>
     * <B>Description:</B><BR>
     * This constructor is the only one for this class and is private because it
     * is a singleton. First it creates the DailyLogThread instance for background
     * log file writing, and then we lower its priority to the minimum since it
     * is a service thread and others threads should take their timeslices first.
     * Then it start()'s the background thread. This constructor is only called
     * once since this is a singleton class, this also means that there is only
     * one background thread.
     * @see DailyLogThread DailyLogThread
     */
    private DailyLog(){
        logDirPath = "logs";
        debugLevel = DEBUGLEVEL_OFF;
        theLogQueue = new LinkedList();
        theLogWriter = new Thread(this);
        theLogWriter.setPriority(Thread.MIN_PRIORITY);
        theLogWriter.start();
    }
    
    //=========================================================================
    //                             Public Methods
    //=========================================================================
    /**
     * <H4>Returns a DailyLog reference to this singleton object.</H4>
     * <B>Description:</B><BR>
     * This overloaded method allows the caller to obtain a reference to
     * this singleton DailyLog object. An optional log directory path, log name, and/or an
     * optional log level can be passed.<BR><BR>
     * &gt;Note: The optional parameters should only be passed once by the main()
     * applicaton method. These parameters can be passed at this time because
     * of the thread of control (order of execution) during class/object construction.
     * In other words, before the background thread needs to use these parameters
     * they will have been saved.
     * @param logDir The path to the log directory where log files will be written.
     * @param logID The identifier that will be combined with the date to create the log's file name.
     * @param level The level of debugging to use.
     * @return a Log reference to this singleton object.
     * @see DailyLog DailyLog
     */
    public static DailyLog getInstance(String logDir, String logID, int level){
        logDirPath = logDir;
        DailyLog.logID = logID;
        debugLevel = level;
        return refSingleton;
    }
    
    /**
     * <H4>Returns a DailyLog reference to this singleton object.</H4>
     * <B>Description:</B><BR>
     * This overloaded method allows the caller to obtain a reference to
     * this singleton DailyLog object. An optional log directory path, log name, and/or an
     * optional log level can be passed.<BR><BR>
     * &gt;Note: The optional parameters should only be passed once by the main()
     * applicaton method. These parameters can be passed at this time because
     * of the thread of control (order of execution) during class/object construction.
     * In other words, before the background thread needs to use these parameters
     * they will have been saved.
     * @param logDir The path to the log directory where log files will be written.
     * @param logID The identifier that will be combined with the date to create the log's file name.
     * @return a DailyLog reference to this singleton object.
     * @see DailyLog DailyLog
     */
    public static DailyLog getInstance(String logDir, String logID){
        logDirPath = logDir;
        DailyLog.logID = logID;
        return refSingleton;
    }
    
    /**
     * <H4>Returns a DailyLog reference to this singleton object.</H4>
     * <B>Description:</B><BR>
     * This overloaded method allows the caller to obtain a reference to
     * this singleton DailyLog object. An optional log directory path, log name, and/or an
     * optional log level can be passed.<BR><BR>
     * &gt;Note: The optional parameters should only be passed once by the main()
     * applicaton method. These parameters can be passed at this time because
     * of the thread of control (order of execution) during class/object construction.
     * In other words, before the background thread needs to use these parameters
     * they will have been saved.
     * @param level The level of debugging to use.
     * @return a Log reference to this singleton object.
     * @see DailyLog DailyLog
     */
    public static DailyLog getInstance(int level){
        debugLevel = level;
        return refSingleton;
    }
    
    /**
     * <H4>Returns a DailyLog reference to this singleton object.</H4>
     * <B>Description:</B><BR>
     * This overloaded method allows the caller to obtain a reference to
     * this singleton DailyLog object. An optional log directory path, log name, and/or an
     * optional log level can be passed.<BR><BR>
     * &gt;Note: The optional parameters should only be passed once by the main()
     * applicaton method. These parameters can be passed at this time because
     * of the thread of control (order of execution) during class/object construction.
     * In other words, before the background thread needs to use these parameters
     * they will have been saved.
     * @return a Log reference to this singleton object.
     * @see DailyLog DailyLog
     */
    public static DailyLog getInstance(){
        return refSingleton;
    }
    
    /**
     * <H4>Adds a log entry to the log queue at the debug level.</H4>
     * <B>Description:</B><BR>
     * This method allows the caller to add a log enter to the log queue at the
     * debug level of logging. This level is typically used for displaying
     * program information of a debug nature. You can give the Debug methods
     * either a string message or a int DebugPoint value for simple tracing.
     * Debug method are provided for FINE (debug1), FINER (debug2), and FINEST
     * (debug3).
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param msg a String giving the log message.
     * @see #debug debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     * @see #info info
     * @see #warn warn
     * @see #error error
     * @see DailyLogThread#addEntry DailyLogThread.addEntry
     */
    public void debug(String sourceClass, String sourceMethod, String msg){
        if (debugLevel >= DEBUGLEVEL_ON){
            addEntry(sourceClass, sourceMethod, msg, "D");
        }
    }
    
    /**
     * <H4>Adds a log entry to the log queue at the debug level.</H4>
     * <B>Description:</B><BR>
     * This method allows the caller to add a log enter to the log queue at the
     * debug level of logging. This level is typically used for displaying
     * program information of a debug nature. You can give the Debug methods
     * either a string message or a int DebugPoint value for simple tracing.
     * Debug method are provided for FINE (debug1), FINER (debug2), and FINEST
     * (debug3).
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param msg a String giving the log message.
     * @see #debug debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     * @see #info info
     * @see #warn warn
     * @see #error error
     * @see DailyLogThread#addEntry DailyLogThread.addEntry
     */
    public void debug1(String sourceClass, String sourceMethod, String msg){
        if (debugLevel >= DEBUGLEVEL_FINE){
            addEntry(sourceClass, sourceMethod, msg, "1");
        }
    }
    
    /**
     * <H4>Adds a log entry to the log queue at the debug level.</H4>
     * <B>Description:</B><BR>
     * This method allows the caller to add a log enter to the log queue at the
     * debug level of logging. This level is typically used for displaying
     * program information of a debug nature. You can give the Debug methods
     * either a string message or a int DebugPoint value for simple tracing.
     * Debug method are provided for FINE (debug1), FINER (debug2), and FINEST
     * (debug3).
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param num a int giving DebugPoint value.
     * @see #debug debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     * @see #info info
     * @see #warn warn
     * @see #error error
     * @see DailyLogThread#addEntry DailyLogThread.addEntry
     */
    public void debug1(String sourceClass, String sourceMethod, int num){
        if (debugLevel >= DEBUGLEVEL_FINE){
            addEntry(sourceClass, sourceMethod, "DebugPoint-" + String.valueOf(num), "1");
        }
    }
    
    /**
     * <H4>Adds a log entry to the log queue at the debug level.</H4>
     * <B>Description:</B><BR>
     * This method allows the caller to add a log enter to the log queue at the
     * debug level of logging. This level is typically used for displaying
     * program information of a debug nature. You can give the Debug methods
     * either a string message or a int DebugPoint value for simple tracing.
     * Debug method are provided for FINE (debug1), FINER (debug2), and FINEST
     * (debug3).
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param msg a String giving the log message.
     * @see #debug debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     * @see #info info
     * @see #warn warn
     * @see #error error
     * @see DailyLogThread#addEntry DailyLogThread.addEntry
     */
    public void debug2(String sourceClass, String sourceMethod, String msg){
        if (debugLevel >= DEBUGLEVEL_FINER){
            addEntry(sourceClass, sourceMethod, msg, "2");
        }
    }
    
    /**
     * <H4>Adds a log entry to the log queue at the debug level.</H4>
     * <B>Description:</B><BR>
     * This method allows the caller to add a log enter to the log queue at the
     * debug level of logging. This level is typically used for displaying
     * program information of a debug nature. You can give the Debug methods
     * either a string message or a int DebugPoint value for simple tracing.
     * Debug method are provided for FINE (debug1), FINER (debug2), and FINEST
     * (debug3).
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param num a int giving DebugPoint value.
     * @see #debug debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     * @see #info info
     * @see #warn warn
     * @see #error error
     * @see DailyLogThread#addEntry DailyLogThread.addEntry
     */
    public void debug2(String sourceClass, String sourceMethod, int num){
        if (debugLevel >= DEBUGLEVEL_FINER){
            addEntry(sourceClass, sourceMethod, "DebugPoint-" + String.valueOf(num), "2");
        }
    }
    
    /**
     * <H4>Adds a log entry to the log queue at the debug level.</H4>
     * <B>Description:</B><BR>
     * This method allows the caller to add a log enter to the log queue at the
     * debug level of logging. This level is typically used for displaying
     * program information of a debug nature. You can give the Debug methods
     * either a string message or a int DebugPoint value for simple tracing.
     * Debug method are provided for FINE (debug1), FINER (debug2), and FINEST
     * (debug3).
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param msg a String giving the log message.
     * @see #debug debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     * @see #info info
     * @see #warn warn
     * @see #error error
     * @see DailyLogThread#addEntry DailyLogThread.addEntry
     */
    public void debug3(String sourceClass, String sourceMethod, String msg){
        if (debugLevel >= DEBUGLEVEL_FINEST){
            addEntry(sourceClass, sourceMethod, msg, "3");
        }
    }
    
    /**
     * <H4>Adds a log entry to the log queue at the debug level.</H4>
     * <B>Description:</B><BR>
     * This method allows the caller to add a log enter to the log queue at the
     * debug level of logging. This level is typically used for displaying
     * program information of a debug nature. You can give the Debug methods
     * either a string message or a int DebugPoint value for simple tracing.
     * Debug method are provided for FINE (debug1), FINER (debug2), and FINEST
     * (debug3).
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param num a int giving DebugPoint value.
     * @see #debug debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     * @see #info info
     * @see #warn warn
     * @see #error error
     * @see DailyLogThread#addEntry DailyLogThread.addEntry
     */
    public void debug3(String sourceClass, String sourceMethod, int num){
        if (debugLevel >= DEBUGLEVEL_FINEST){
            addEntry(sourceClass, sourceMethod, "DebugPoint-" + String.valueOf(num), "3");
        }
    }
    
    /**
     * <H4>Adds a log entry to the log queue at the information level.</H4>
     * <B>Description:</B><BR>
     * This method allows the caller to add a log enter to the log queue at the
     * information level of logging. This level is typically used to record
     * normal processing events.
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param msg a String giving the log message.
     * @see #debug debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     * @see #warn warn
     * @see #error error
     * @see DailyLogThread#addEntry DailyLogThread.addEntry
     */
    public void info(String sourceClass, String sourceMethod, String msg){
        addEntry(sourceClass, sourceMethod, msg, "I");
    }
    
    /**
     * <H4>Adds a log entry to the log queue at the warning level.</H4>
     * <B>Description:</B><BR>
     * This method allows the caller to add a log enter to the log queue at the
     * warning level of logging. This level is typically used to record
     * significant processing events of a cautionary nature such as low
     * resources.
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param msg a String giving the log message.
     * @see #debug debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     * @see #info info
     * @see #error error
     * @see DailyLogThread#addEntry DailyLogThread.addEntry
     */
    public void warn(String sourceClass, String sourceMethod, String msg){
        addEntry(sourceClass, sourceMethod, msg, "W");
    }
    
    public void warn(String sourceClass, String sourceMethod, Exception e){
        String msg = formatException(e);
        addEntry(sourceClass, sourceMethod, msg, "W");
    }
    
    /**
     * <H4>Adds a log entry to the log queue at the error level.</H4>
     * <B>Description:</B><BR>
     * This method allows the caller to add a log enter to the log queue at the
     * error level of logging. This level is typically used to record a failure
     * to perform to the required system norms.
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param msg a String giving the log message.
     * @see #debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     * @see #info
     * @see #warn
     * @see DailyLogThread#addEntry DailyLogThread.addEntry
     */
    public void error(String sourceClass, String sourceMethod, String msg){
        addEntry(sourceClass, sourceMethod, "*** " + msg, "E");
    }
    
    public void error(String sourceClass, String sourceMethod, Exception e){
        String msg = formatException(e);
        error(sourceClass, sourceMethod, msg);
    }
    
    private String formatException(Exception e){
        StringBuffer retVal = new StringBuffer();
        retVal.append(e.toString());
        StackTraceElement ste[] = e.getStackTrace();
        for (int i = 0; i < ste.length; i++){
            retVal.append(NL);
            retVal.append(ste[i].toString());
        }
        return retVal.toString();
    }
    
    /**
     * <H4>Sets the debug level for logging.</H4>
     * <B>Description:</B><BR>
     * This class method sets the debugLevel for logging. First it checks
     * if the requested level is in the range of levels, if so the new level is
     * set.
     * @param level an int giving the desired debug level.
     * @see #debug debug
     * @see #debug1 debug1
     * @see #debug2 debug2
     * @see #debug3 debug3
     */
    public void setDebugLevel(int level){
        final String sM = "setDebugLevel()";
        if (level >= DEBUGLEVEL_OFF && level <= DEBUGLEVEL_FINEST){
            debugLevel = level;
            addEntry(sC, sM, ">>>Log Debug Level: " + String.valueOf(level), "I");
        }
    }
    
    /**
     * <H4>Causes the background thread to close & flush the log file.</H4>
     * <B>Description:</B><BR>
     * This method causes the background thread to close & flush the log file.
     * First it invokes the freeBusy() method informing the background thread
     * that we wish to close the log file and have the thread end, then it
     * join()'s with the background thread causing the method to block until
     * the backgound thread has ended.
     * @see DailyLogThread#freeBusy DailyLogThread.freeBusy
     */
    public void close(){
        try{
            freeBusy();
            theLogWriter.join();
        } catch (Exception ex){
            ex.printStackTrace();
        }
    }
    
    //=========================================================================
    //                             Public Methods
    //=========================================================================
    /**
     * <H4>Processes queued log entries to the log file.</H4>
     * <B>Description:</B><BR>
     * This method is the main entry point for this thread instance and is
     * start()'ed by the DailyLog class's constructor. It consumes log entries
     * from the log queue and writes them to the log file. First it enters the
     * main processing loop and stays there while busy and the log queue has
     * entries to write. In the loop it gets a log entry from the log queue
     * (blocks/waits if empty), it checks if we need to open/change the log file,
     * writes it to the log file, and if the queue is empty flushes the stream.
     * Once the busy flag is reset and the log queue is empty then it closes
     * the log file, and this run()'ing thread is finished.
     * @see DailyLog DailyLog
     * @see #logOpen logOpen
     * @see #getEntry getEntry
     * @see #logWriteLn logWriteLn
     * @see #logFlush logFlush
     * @see #logClose logClose
     */
    public void run(){
        String entryDate = null;
        while (busy || !theLogQueue.isEmpty()){
            String entry = this.getEntry();
            //---Extract entryDate
            if (entry != null){
                if (entry.startsWith(NL)){
                    int i = NL.length();
                    entryDate = entry.substring(i, i + 10);
                } else{
                    entryDate = entry.substring(0, 10);
                }
                //---Check if we need to open or change the log file.
                if (entryDate.compareTo(logDate) != 0){
                    logDate = entryDate;
                    if (logFile != null){
                        logClose();
                    }
                    logOpen();
                }
                //---Write log entry to log file.
                logWriteLn( (String) entry);
            }
            
            //---If queue is empty flush stream.
            if (theLogQueue.isEmpty()){
                logFlush();
            }
        }
        logClose();
    }
    
    /**
     * <H4>Frees the busy flag instructing the background thread to finish up.</H4>
     * <B>Description:</B><BR>
     * This method instructs the run()'ing thread that log writing services are being
     * terminated. First the busy flag is set to false. It then syncs with the log
     * queue and notify()'s the run()'ing thread should it be wait()'ing.
     * The run()'ing thread will sease processing once the busy flag is false and
     * the log queue is empty.
     * @see #run run
     */
    private void freeBusy(){
        busy = false;
        synchronized (theLogQueue){
            theLogQueue.notify();
        }
    }
    
    //=========================================================================
    //                             Private Methods
    //=========================================================================
    /**
     * <H4>Writes an log open entry and flushes the I/O buffer.</H4>
     * <B>Description:</B><BR>
     * This method encapulates the try-catch for attempting I/O on the log file.
     * First it checks if the log directory path exists if not it creates it,
     * then it opens a new FileWriter for appending to the log file. Then it
     * writes a formated entry stateing that the log is open and then flushes
     * the I/O buffer.
     * @see #run run
     * @see #logOpen logOpen
     * @see #logWriteLn logWriteLn
     * @see #logFlush logFlush
     * @see #logClose logClose
     */
    private void logOpen(){
        final String sM = "logOpen()";
        try{
            //---Check if the log directory path exists, if not create it.
            File logDir = new File(DailyLog.logDirPath);
            if (!logDir.exists()){
                logDir.mkdirs();
            }
            //---Open a new log file (will append if pre-existing) and make the first entry.
            logFile = new FileWriter(DailyLog.logDirPath + "/" + DailyLog.logID + "_" + logDate + ".log", true);
            logWriteLn(formatEntry(sC, sM, ">>>Log Opened.", "I"));
            logFlush();
        } catch (IOException ioe){
            ioe.printStackTrace();
        }
    }
    
    /**
     * <H4>Adds a log entry to the log queue.</H4>
     * <B>Description:</B><BR>
     * This method along with getEntry() manages the log queue. It synchronizes on
     * this queue to allow multiple threads access to the queue, and to prevent
     * writting and reading from the queue simultaneously. First it syncs on the log
     * 1ueue, then it formats the log entry, adds it to the queue (FIFO), and performs
     * a notify() to wake run()'ing thread should it be wait()'ing.<BR><BR>
     * &gt;Note: That formatEntry() is done here while the calling thread is scynchronized
     * so that entries in the file will be in timestamp order.
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param msg a String giving the log message.
     * @param level a String giving the log level (i.e. "D","I","W","E")
     * @see #run run
     * @see #getEntry getEntry
     * @see #formatEntry formatEntry
     */
    private void addEntry(String sourceClass, String sourceMethod, String msg, String level){
        String entry = formatEntry(sourceClass, sourceMethod, msg, level);
        synchronized (theLogQueue){
            theLogQueue.add(entry);
            theLogQueue.notify();
        }
    }
    
    /**
     * <H4>Writes a log entry to the log file with appended new line.</H4>
     * <B>Description:</B><BR>
     * This method encapulates the try-catch for attempting I/O on the log file.
     * It writes a formated entry to the log file with an appended new line.
     * @param entry a String formated as a log entry to be written to the log file.
     * @see #run run
     * @see #logOpen logOpen
     * @see #logFlush logFlush
     * @see #logClose logClose
     */
    private void logWriteLn(String entry){
        try{
            logFile.write(entry + NL);
            //System.out.println(entry);
        } catch (IOException ioe){
            ioe.printStackTrace();
        }
    }
    
    /**
     * <H4>Flushes the I/O buffer for the log file.</H4>
     * <B>Description:</B><BR>
     * This method encapulates the try-catch for attempting I/O on the log file.
     * It flushes the I/O buffer associated with the log file.
     * @see #run run
     * @see #logOpen logOpen
     * @see #logWriteLn logWriteLn
     * @see #logClose logClose
     */
    private void logFlush(){
        try{
            logFile.flush();
        } catch (IOException ioe){
            ioe.printStackTrace();
        }
    }
    
    /**
     * <H4>Writes a log closed entry and flushes the I/O buffer.</H4>
     * <B>Description:</B><BR>
     * This method encapulates the try-catch for attempting I/O on the log file.
     * First it writes a formated entry stating that the log is closed and then
     * flushes the I/O buffer.
     * @see #run run
     * @see #logOpen logOpen
     * @see #logWriteLn logWriteLn
     * @see #logFlush logFlush
     */
    private void logClose(){
        final String sM = "logClose()";
        logWriteLn(formatEntry(sC, sM, ">>>Log Closed.", "I"));
        logFlush();
    }
    
    /**
     * <H4>Fetches or Waits for the next log entry from the log queue.</H4>
     * <B>Description:</B><BR>
     * This method along with addEntry() manages the log queue. It synchronizes on
     * this queue to allow multiple threads access to the queue, and to prevent
     * writting and reading from the queue simultaneously. First it syncs on the log
     * queue to protect from colition. This it check if it would enter a wait()
     * state, still busy but the log queue is empty.  After waiting if there is an
     * item on the queue the oldest item is removed (FIFO) and returned to the caller.
     * @return a Object from the log queue or null.
     * @see #run run
     * @see #addEntry addEntry
     */
    private String getEntry(){
        String entry = null;
        synchronized (theLogQueue){
            if (busy && theLogQueue.isEmpty()){
                try{
                    theLogQueue.wait();
                } catch (InterruptedException ie){
                }
            }
            if (!theLogQueue.isEmpty()){
                entry = (String) theLogQueue.remove(0);
            }
        }
        return entry;
    }
    
    /**
     * <H4>Formats a log entry line for output.</H4>
     * <B>Description:</B><BR>
     * This method returns a string object in log format. It combines the current timestamp,
     * log level, thread name, class name, method name, and log message into a single line
     * log entry.
     * @param sourceClass a String giving the name of the calling class.
     * @param sourceMethod a String giving the name of the calling method.
     * @param msg a String giving the log message.
     * @param level a String giving the log level (i.e. "D","I","W","E")
     * @return a String in log format.
     * @see #addEntry addEntry
     * @see #getTimeStamp getTimeStamp
     * @see #padString padString
     */
    private String formatEntry(String sourceClass, String sourceMethod, String msg, String level){
        String threadName = Thread.currentThread().getName();
        StringBuffer entry = new StringBuffer();
        if (DailyLog.debugLevel >= DailyLog.DEBUGLEVEL_ON && prevThreadName.compareTo(threadName) != 0){
            entry.append(NL);
            prevThreadName = threadName;
        }
        entry.append(getTimeStamp());
        entry.append(" ");
        entry.append(level);
        entry.append(" ");
        entry.append(padString(threadName, 25));
        entry.append(" ");
        entry.append(padString(sourceClass + ":" + sourceMethod, 50));
        entry.append(" ");
        entry.append(msg);
        return entry.toString();
    }
    
    /**
     * <H4>Returns the current time as a formatted string.</H4>
     * <B>Description:</B><BR>
     * This method returns the current timestamp formatted using a SimpleDateFormat
     * "yyyy-MM-dd HH:mm:ss.SSS".
     * @return a String in yyyy-MM-dd HH:mm:ss.SSS format.
     * @see #formatEntry formatEntry
     * @see SimpleDateFormat SimpleDateFormat
     */
    private String getTimeStamp(){
        /** @todo Use the GregorianCalendar and set the time zone to GMT */
        return sdfDateTime.format(new Date());
    }
    
    /**
     * <H4>Pads a string with spaces to the requested length.</H4>
     * <B>Description:</B><BR>
     * This method returns a string object that is left justified and space filled of the
     * length requested by the caller. It has a constant SPACES that holds 150 spaces for
     * padding the input string. It append the input string with SPACES and takes a
     * substring of the requested length.
     * @param str a String pad spaces to.
     * @param length an int giving the length of the resulting String returned.
     * @return a String left justified space filled of the requested length.
     * @see #formatEntry formatEntry
     */
    private String padString(String str, int length){
        /** a String constant that holds 150 spaces for appending. */
        final String SPACES =
                "                                                                                                                                                      "; //150spaces
        String strPadded = str + SPACES;
        strPadded = strPadded.substring(0, length);
        return strPadded;
    }
    
}
